/*
 * Copyright (c) 2010 Mathew Hall, University of Sheffield.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following conditions
 * are met:
 *
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following
 * disclaimer in the documentation and/or other materials provided
 * with the distribution.
 *
 * Neither the name of the University of Sheffield nor the names of its
 * contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
package primitives.graph;

import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

public class Node implements Serializable {

    private static final long serialVersionUID = 1L;
    private String label;
    private HashMap<Node, Set<Transition>> neighbours;
    Set<Transition> tcache;
    public int refCount = 0;

    public Node() {
        neighbours = new HashMap<Node, Set<Transition>>();
        label = "<undefined>";
        tcache = null;
    }

    public Node(String label) {
        this();
        this.label = label;
    }

    public String getLabel() {
        return label;
    }

    public void connect(Node n, String l) {
        connect(n, l, false);
    }

    /**
     * Removes an edge from this node.
     * @param n The neighbour to be deleted.
     */
    public void unlink(Node n) {
        if (neighbours.get(n) == null) {
            return;
        }
        neighbours.remove(n);
        tcache = null;
    }

    public boolean hasTransitionWithLabel(String l) {
        try {
            transitionWithLabel(l);
            return true;
        } catch (TransitionNotFoundException ex) {
            return false;
        }
    }

    public Transition transitionWithLabel(String l) throws TransitionNotFoundException {
        for (Node n : neighbours.keySet()) {
            for (Transition t : neighbours.get(n)) {

                if (l.equals(t.getLabel())) {
                    return t;
                }
            }
        }
        throw new TransitionNotFoundException(l);
    }

    public boolean pathTo(Node n) {
        return neighbours.containsKey(n);
    }

    public Set<Transition> getTransitionsAsT() {
        if (tcache == null) {
            tcache = new HashSet<Transition>();

            for (Node n : neighbours.keySet()) {
                tcache.addAll(neighbours.get(n));
            }
        }


        return tcache;
    }

    /**
     * Get a set of nodes that this node has transitions to.
     * 
     * @return a Set of Node objects
     */
    public Set<Node> getTransitionsAsN() {
        return neighbours.keySet();
    }

    public Set<String> getTransitionsAsS() {
        Set<String> ret = new HashSet<String>();
        for (Node n : neighbours.keySet()) {
            for (Transition t : neighbours.get(n)) {
                ret.add(t.getLabel());
            }
        }
        return ret;
    }

    public void setLabel(String label) {
        this.label = label;
    }

    public void addTransition(Transition t) {
        if (neighbours.get(t.getDestinationNode()) == null) {
            neighbours.put(t.getDestinationNode(), new HashSet<Transition>());
        }
        neighbours.get(t.getDestinationNode()).add(t);
        tcache = null;
    }

    public void deleteTransition(Transition t) {

        if (neighbours.containsKey(t.getDestinationNode())) {
            neighbours.get(t.getDestinationNode()).remove(t);
            if (t.getDestinationNode() != this) {
                t.getDestinationNode().refCount = t.getDestinationNode().refCount - 1;
            }
            tcache = null;
        }




    }

    public int labelCount(String label) {
        int ret = 0;
        for (Node n : neighbours.keySet()) {
            for (Transition t : neighbours.get(n)) {

                if (label.equals(t.getLabel())) {
                    ret++;
                }
            }
        }
        return ret;
    }

    public void connect(Node n, String l, Boolean isSuperstate) {

        if (neighbours.get(n) == null) {
            neighbours.put(n, new HashSet<Transition>());
        } else {
            for (Transition t : neighbours.get(n)) {
                if (t.getLabel().equals(l)) {
                    return;
                }
            }
        }
        Transition t = new Transition(this, n, l);
        if (n != this) {
            n.refCount = n.refCount + 1;
        }
        t.setSuperstate(isSuperstate);
        neighbours.get(n).add(t);
        tcache = null;
    }

    @Override
    public Node clone() {


        return new Node(label);
    }

    @Override
    public String toString() {
        String l = "";
        if (neighbours.isEmpty()) {
            l = "EMPTY";
        } else {
            for (Node n : neighbours.keySet()) {
                l += n.getLabel() + ",";
            }
        }
        return "Node [label=" + label + ", neighbours=" + l + "]";
    }
/*
    @Override
    public int hashCode(){
        int hash = 1;
        hash *= 37;
        hash += label.hashCode();
        hash *= 37;
        hash += neighbours.hashCode();
        return hash;
    }
    */
}
